package midend;

import midend.IR_Instruction.*;
import midend.IR_Type.*;
import midend.IR_Value.*;

import java.util.ArrayList;
import java.util.List;

public class IR_Builder {
    private IR_BaseBlock currentbb = new IR_BaseBlock("root",0);
    private static IR_Type i32 = IR_Int32.IR_Int32();
    private static IR_Type i1 = IR_Int1.IR_Int1();
    private static IR_Type Float = IR_Float.IR_Float();
    private static IR_Type Void = IR_Void.IR_Void();

    public IR_Builder() {
    }

    public static void setbuilderbb(IR_Builder builder,IR_BaseBlock bb){
       builder.currentbb = bb;
    }

    public static IR_BaseBlock getbuilderbb(IR_Builder builder){
        return builder.currentbb;
    }

    public static void IR_Atend(IR_Builder builder,IR_BaseBlock bb){
        builder.currentbb.add_nextList(bb);  //添加后继
        builder.currentbb.add_NextBlock(bb);
        bb.add_preList(builder.currentbb);//添加前驱
        bb.add_PreBlock(builder.currentbb);
        builder.currentbb = bb;
    }
    public static void IR_Atendwithprenext(IR_BaseBlock bb1,IR_BaseBlock bb2){
        if(!bb1.getNextBlock().contains(bb2)) {
            bb1.getNextBlock().add(bb2);
            bb2.getPreBlock().add(bb1);
        }
    }
    public static void IR_deletewithprenext(IR_BaseBlock bb1,IR_BaseBlock bb2){
        if(bb1.getNextBlock().contains(bb2)) {
            bb1.getNextBlock().remove(bb2);
            bb2.getPreBlock().remove(bb1);
        }
    }

    public static IR_Value Alloc(IR_Builder builder, IR_Type typ, String name){
        IR_Value ans = null;
        IR_Pointer p = new IR_Pointer(typ);
        ans = new IR_Local_Value(name,p);
        Instruction instruction = new Alloc_Instrction(Gen_op(ans),builder.currentbb);
        builder.add_Instruction(instruction);
        //builder.appendln(instruction.toString(),4);
        return ans;
    }

    public static IR_Value Store(IR_Builder builder,IR_Value value,IR_Value p){
        IR_Value ans = null;
        IR_Pointer p_Type;
        if(value.get_Type() == i32 && ((IR_Pointer)p.get_Type()).get_Base_Type() == Float){
            //int 转 float
            value = transfer(builder,value,IR_Const.IntToFloat);
            p_Type= new IR_Pointer(value.get_Type());
        }
        else if(value.get_Type() == Float && ((IR_Pointer)p.get_Type()).get_Base_Type() == i32){
            // float 转 i32
            p_Type = new IR_Pointer(value.get_Type());
        }
        else {
            p_Type = new IR_Pointer(value.get_Type());
        }
        ans = new IR_Local_Value("Storetmp",p_Type);
        Instruction instruction = new Store_Instruction(Gen_op(ans,value,p),builder.currentbb);
        builder.add_Instruction(instruction);
        //builder.appendln(instruction.toString(),4);
        return ans;
    }
    public static IR_Value transfer(IR_Builder builder,IR_Value value,int typ){
        IR_Value ret = null;
        if(typ == IR_Const.IntToFloat){
            ret = new IR_Local_Value("transfertemp" ,Float);
            Instruction instruction = new Type_Trans_Instruction(Gen_op(ret,value),builder.currentbb,IR_Const.IntToFloat);
            builder.add_Instruction(instruction);
            //builder.appendln(instruction.toString());
        }
        else if(typ == IR_Const.FloatToInt){
            ret = new IR_Local_Value("transfertemp",i32);
            Instruction instruction = new Type_Trans_Instruction(Gen_op(ret,value),builder.currentbb,IR_Const.FloatToInt);
            builder.add_Instruction(instruction);
            //builder.appendln(instruction.toString());
        }
        else if (typ == IR_Const.BITCAST){
            ret = new IR_Local_Value("transfertemp",new IR_Pointer(i32));
            Instruction instruction = new Type_Trans_Instruction(Gen_op(ret,value),builder.currentbb,IR_Const.BITCAST);
            builder.add_Instruction(instruction);
            //builder.appendln(instruction.toString());
        }
        return ret;
    }

    public static IR_Value Ret(IR_Builder builder, IR_Value value){
        Instruction instruction = new Ret_Instruction(Gen_op(value),builder.currentbb);
        builder.add_Instruction(instruction);
        //builder.appendln(instruction.toString());
        return null;
    }


    public static IR_Value Load(IR_Builder builder, String name,IR_Value value){
        //获取指针的基类
        IR_Type basetype = ((IR_Pointer) value.get_Type()).get_Base_Type();
        IR_Value ans = new IR_Local_Value(name,basetype);
        Instruction instruction = new Load_Instruction(Gen_op(ans,value),builder.currentbb);
        builder.add_Instruction(instruction);
        //builder.appendln(instruction.toString(),4);
        return ans;
    }

    public static IR_Value Get_Pointer(IR_Builder builder,IR_Value value,List<IR_Value> list,int id,String name){
        IR_Type res = null;
        // 首先判断 value的类型
        IR_Type baseType;
        if(value.get_Type() instanceof IR_Array){
            baseType = ((IR_Array) value.get_Type()).get_Array_Type();
        }else {
            baseType = ((IR_Pointer) value.get_Type()).get_Base_Type();
        }
        //id 是区分的  当id是 1 的时候是 读取指针里面的变量 的数据
        if (baseType instanceof IR_Array && id != 1) {
            res = ((IR_Array) baseType).get_Array_Type();
        } else {
            res = baseType;
        }
        res = new IR_Pointer(res);
        IR_Value ans = new IR_Local_Value(name, res);
        //构造需要进行传递的参数
        List<IR_Value> operands = new ArrayList<>();
        operands.add(ans);
        operands.add(value);
        for(IR_Value i:list){
            operands.add(i);
        }
        Instruction instruction = new Pointer_Instruction(operands,builder.currentbb);
        builder.add_Instruction(instruction);
        //builder.appendln(instruction.toString());
        return ans;
    }
    public void appendln(String s){
        currentbb.appendln(s);
    }
    public void appendln(String s,int align){
        currentbb.appendln(s,align);
    }
    private void add_Instruction(Instruction ins){
        this.currentbb.add_InstructionList(ins);
    }

    private static List<IR_Value> Gen_op(IR_Value... values){
        List<IR_Value> ans = new ArrayList<>();
        for(IR_Value value:values){
            ans.add(value);
        }
        return ans;
    }

    public static IR_Value Add(IR_Builder builder,IR_Value value1,IR_Value value2,String op){
        //常量直接计算结果返回
        if(value1 instanceof IR_Int_Value && value2 instanceof IR_Int_Value){
            int ans = ((IR_Int_Value) value1).get_Int_Value() + ((IR_Int_Value) value2).get_Int_Value();
            return new IR_Int_Value(ans,i32);
        }
        else if(value1 instanceof IR_Float_Value && value2 instanceof IR_Float_Value){
            float ans = ((IR_Float_Value) value1).get_Float_Value() + ((IR_Float_Value) value2).get_Float_Value();
            return new IR_Float_Value(ans);
        }
        else if(value1 instanceof IR_Int_Value && value2 instanceof IR_Float_Value){
            float ans = ((IR_Int_Value) value1).get_Int_Value() + ((IR_Float_Value) value2).get_Float_Value();
            return new IR_Float_Value(ans);
        }
        else if(value1 instanceof IR_Float_Value && value2 instanceof IR_Int_Value){
            float ans = ((IR_Float_Value) value1).get_Float_Value() + ((IR_Int_Value) value2).get_Int_Value();
            return new IR_Float_Value(ans);
        }
        else{
            IR_Value ans = null;
            //类型转化
            if(i32 == value1.get_Type() && Float == value2.get_Type()){
                value1 = transfer(builder,value1,IR_Const.IntToFloat);
            }
            else if(Float == value1.get_Type() && i32 == value2.get_Type()){
                value2 = transfer(builder,value2,IR_Const.IntToFloat);
            }
            ans = new IR_Local_Value(op, value1.get_Type());
            Instruction instruction;
            if(i32 == value1.get_Type() && i32 == value2.get_Type()){
                instruction = new Calc_Instruction(Gen_op(ans,value1,value2),builder.currentbb,"add");
            }
            else{
                instruction = new Calc_Instruction(Gen_op(ans,value1,value2),builder.currentbb,"fadd");
            }
            builder.add_Instruction(instruction);
            //builder.appendln(instruction.toString());
            return ans;
        }
    }

    public static IR_Value Sub(IR_Builder builder,IR_Value value1,IR_Value value2,String op) {
        //常量直接计算结果返回
        if (value1 instanceof IR_Int_Value && value2 instanceof IR_Int_Value) {
            int ans = ((IR_Int_Value) value1).get_Int_Value() - ((IR_Int_Value) value2).get_Int_Value();
            return new IR_Int_Value(ans,i32);
        } else if (value1 instanceof IR_Float_Value && value2 instanceof IR_Float_Value) {
            float ans = ((IR_Float_Value) value1).get_Float_Value() - ((IR_Float_Value) value2).get_Float_Value();
            return new IR_Float_Value(ans);
        } else if (value1 instanceof IR_Int_Value && value2 instanceof IR_Float_Value) {
            float ans = ((IR_Int_Value) value1).get_Int_Value() - ((IR_Float_Value) value2).get_Float_Value();
            return new IR_Float_Value(ans);
        } else if (value1 instanceof IR_Float_Value && value2 instanceof IR_Int_Value) {
            float ans = ((IR_Float_Value) value1).get_Float_Value() - ((IR_Int_Value) value2).get_Int_Value();
            return new IR_Float_Value(ans);
        } else {
            IR_Value ans = null;
            //类型转化
            if (i32 == value1.get_Type() && Float == value2.get_Type()) {
                value1 = transfer(builder, value1, IR_Const.IntToFloat);
            } else if (Float == value1.get_Type() && i32 == value2.get_Type()) {
                value2 = transfer(builder, value2, IR_Const.IntToFloat);
            }
            ans = new IR_Local_Value(op, value1.get_Type());
            Instruction instruction;
            if (i32 == value1.get_Type() && i32 == value2.get_Type()) {
                instruction = new Calc_Instruction(Gen_op(ans, value1, value2), builder.currentbb, "sub");
            } else {
                instruction = new Calc_Instruction(Gen_op(ans, value1, value2), builder.currentbb, "fsub");
            }
            builder.add_Instruction(instruction);
            //builder.appendln(instruction.toString());
            return ans;
        }
    }

    public static IR_Value Mul(IR_Builder builder,IR_Value value1,IR_Value value2,String op) {
        //常量直接计算结果返回
        if (value1 instanceof IR_Int_Value && value2 instanceof IR_Int_Value) {
            int ans = ((IR_Int_Value) value1).get_Int_Value() * ((IR_Int_Value) value2).get_Int_Value();
            return new IR_Int_Value(ans,i32);
        } else if (value1 instanceof IR_Float_Value && value2 instanceof IR_Float_Value) {
            float ans = ((IR_Float_Value) value1).get_Float_Value() * ((IR_Float_Value) value2).get_Float_Value();
            return new IR_Float_Value(ans);
        } else if (value1 instanceof IR_Int_Value && value2 instanceof IR_Float_Value) {
            float ans = ((IR_Int_Value) value1).get_Int_Value() * ((IR_Float_Value) value2).get_Float_Value();
            return new IR_Float_Value(ans);
        } else if (value1 instanceof IR_Float_Value && value2 instanceof IR_Int_Value) {
            float ans = ((IR_Float_Value) value1).get_Float_Value() * ((IR_Int_Value) value2).get_Int_Value();
            return new IR_Float_Value(ans);
        } else {
            IR_Value ans = null;
            //类型转化
            if (i32 == value1.get_Type() && Float == value2.get_Type()) {
                value1 = transfer(builder, value1, IR_Const.IntToFloat);
            } else if (Float == value1.get_Type() && i32 == value2.get_Type()) {
                value2 = transfer(builder, value2, IR_Const.IntToFloat);
            }
            ans = new IR_Local_Value(op, value1.get_Type());
            Instruction instruction;
            if (i32 == value1.get_Type() && i32 == value2.get_Type()) {
                instruction = new Calc_Instruction(Gen_op(ans, value1, value2), builder.currentbb, "mul");
            } else {
                instruction = new Calc_Instruction(Gen_op(ans, value1, value2), builder.currentbb, "fmul");
            }
            builder.add_Instruction(instruction);
            //builder.appendln(instruction.toString());
            return ans;
        }
    }

    public static IR_Value Div(IR_Builder builder,IR_Value value1,IR_Value value2,String op) {
        //常量直接计算结果返回
        if (value1 instanceof IR_Int_Value && value2 instanceof IR_Int_Value) {
            int ans = ((IR_Int_Value) value1).get_Int_Value() / ((IR_Int_Value) value2).get_Int_Value();
            return new IR_Int_Value(ans,i32);
        } else if (value1 instanceof IR_Float_Value && value2 instanceof IR_Float_Value) {
            float ans = ((IR_Float_Value) value1).get_Float_Value() / ((IR_Float_Value) value2).get_Float_Value();
            return new IR_Float_Value(ans);
        } else if (value1 instanceof IR_Int_Value && value2 instanceof IR_Float_Value) {
            float ans = ((IR_Int_Value) value1).get_Int_Value() / ((IR_Float_Value) value2).get_Float_Value();
            return new IR_Float_Value(ans);
        } else if (value1 instanceof IR_Float_Value && value2 instanceof IR_Int_Value) {
            float ans = ((IR_Float_Value) value1).get_Float_Value() / ((IR_Int_Value) value2).get_Int_Value();
            return new IR_Float_Value(ans);
        } else {
            IR_Value ans = null;
            //类型转化
            if (i32 == value1.get_Type() && Float == value2.get_Type()) {
                value1 = transfer(builder, value1, IR_Const.IntToFloat);
            } else if (Float == value1.get_Type() && i32 == value2.get_Type()) {
                value2 = transfer(builder, value2, IR_Const.IntToFloat);
            }
            ans = new IR_Local_Value(op, value1.get_Type());
            Instruction instruction;
            if (i32 == value1.get_Type() && i32 == value2.get_Type()) {
                instruction = new Calc_Instruction(Gen_op(ans, value1, value2), builder.currentbb, "sdiv");
            } else {
                instruction = new Calc_Instruction(Gen_op(ans, value1, value2), builder.currentbb, "fdiv");
            }
            builder.add_Instruction(instruction);
            //builder.appendln(instruction.toString());
            return ans;
        }
    }

    public static IR_Value Rem(IR_Builder builder,IR_Value value1,IR_Value value2,String op){
        if(value1 instanceof IR_Int_Value && value2 instanceof IR_Int_Value){
            return new IR_Int_Value(((IR_Int_Value) value1).get_Int_Value() % ((IR_Int_Value) value2).get_Int_Value(),i32);
        }
        else{
            IR_Value ans = null;
            ans = new IR_Local_Value(op, value1.get_Type());
            Instruction instruction = new Calc_Instruction(Gen_op(ans,value1,value2),builder.currentbb,"srem");
            builder.add_Instruction(instruction);
            //builder.appendln(instruction.toString());
            return ans;
        }
    }

    public static IR_Value Neg(IR_Builder builder,IR_Value value,String op){
        if(value instanceof IR_Int_Value){
            return new IR_Int_Value(((IR_Int_Value) value).get_Int_Value() * (-1), value.get_Type());
        }
        else if(value instanceof IR_Float_Value){
            return new IR_Float_Value(((IR_Float_Value) value).get_Float_Value() * (-1), value.get_Type());
        }
        else if(value instanceof  IR_Local_Value){
            if(Float == value.get_Type()){
                return Sub(builder,new IR_Float_Value(0),value,op);
            }
            else{
                return Sub(builder,new IR_Int_Value(0,i32),value,op);
            }
        }
        return null;
    }

    public static IR_Value Icmp(IR_Builder builder,IR_Value value1,IR_Value value2,int cmptype,String name){
        //类型转化
        if (i32 == value1.get_Type() && Float == value2.get_Type()) {
            value1 = transfer(builder, value1, IR_Const.IntToFloat);
        } else if (Float == value1.get_Type() && i32 == value2.get_Type()) {
            value2 = transfer(builder, value2, IR_Const.IntToFloat);
        }

        IR_Value ans = new IR_Local_Value(name, i1);
        Instruction instruction = new Cond_Instruction(Gen_op(ans,value1,value2),builder.currentbb,cmptype);
        builder.add_Instruction(instruction);
        //builder.appendln(instruction.toString());
        return ans;
    }

    public static IR_Value Xor(IR_Builder builder,IR_Value value1,IR_Value value2,String name){
        if(value1 instanceof IR_Int_Value){
            return new IR_Int_Value(((IR_Int_Value) value1).get_Int_Value() ^ 1,i1);
        }
        IR_Value ans = new IR_Local_Value(name, value2.get_Type());
        Instruction instruction = new Calc_Instruction(Gen_op(ans,value1,value2),builder.currentbb,"xor");
        builder.add_Instruction(instruction);
        //builder.appendln(instruction.toString());
        return ans;
    }

    public static IR_Value Zext(IR_Builder builder,IR_Value value,IR_Type type,String name){
        if(value.get_Type() == type){
            return value;
        }
        if(value instanceof IR_Int_Value){
            return new IR_Int_Value(((IR_Int_Value) value).get_Int_Value(),type);
        }
        IR_Value ans = new IR_Local_Value(name,type);
        Instruction instruction = new Zext_Instruction(Gen_op(ans,value,new IR_Int_Value(0,type)),builder.currentbb);
        builder.add_Instruction(instruction);
        //builder.appendln(instruction.toString());
        return ans;
    }

    public static IR_Value Call(IR_Builder builder,IR_FuncBlock funcbb,List<IR_Value> args,int argsnum,String name){
        IR_Type ret =  ((IR_Func)funcbb.get_Type()).get_Return_Type();
        IR_Value ans = new IR_Local_Value(name + "_call",ret);
        List<IR_Value> ops = Gen_op(ans,funcbb);
        for (int i = 0; i < argsnum; i++) {
            ops.add(args.get(i));
        }
        Instruction instruction = new Call_Instruction(ops,builder.currentbb);
        builder.add_Instruction(instruction);
        //builder.appendln(instruction.toString());
        return ans;
    }

    // if else 都有
    public static IR_Value Cond(IR_Builder builder,IR_Value cond,IR_BaseBlock truebb,IR_BaseBlock falsebb){
        Instruction instruction = new Branch_Instruction(Gen_op(cond,truebb,falsebb),builder.currentbb);
        builder.add_Instruction(instruction);
        //builder.appendln(instruction.toString());
        return null;
    }

    //只有 if
    public static IR_Value Cond(IR_Builder builder,IR_Value cond,IR_BaseBlock truebb){
        Instruction instruction = new Branch_Instruction(Gen_op(cond,truebb),builder.currentbb);
        builder.add_Instruction(instruction);
        //builder.appendln(instruction.toString());
        return null;
    }

    public static IR_Value Br(IR_Builder builder,IR_BaseBlock bb){
        Instruction instruction = new Branch_Instruction(Gen_op(bb),builder.currentbb);
        builder.add_Instruction(instruction);
        //builder.appendln(instruction.toString());
        return null;
    }
}
